package com.youlai.boot.core.security.extension.wx;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.core.security.model.SysUserDetails;
import com.youlai.boot.core.security.model.UserAuthCredentials;
import com.youlai.boot.core.tenant.context.TenantContextHolder;
import com.youlai.boot.system.model.entity.GlobalSysUser;
import com.youlai.boot.system.service.GlobalSysUserService;
import com.youlai.boot.system.service.UserService;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;


/**
 * 微信小程序Code认证Provider（支持多租户）
 *
 * 登录逻辑：
 * 1. 首次登录（注册）：在主数据源的 global_sys_user 表创建用户，tenant_id 为 NULL
 * 2. 已注册但无租户：从 global_sys_user 查询，返回无权限的 token
 * 3. 已加入租户：从 global_sys_user 查询，切换到租户数据源，查询权限信息
 *
 * @author 有来技术团队
 * @since 2.0.0
 */
@Slf4j
public class WxMiniAppCodeAuthenticationProvider implements AuthenticationProvider {

    private final UserService userService;
    private final WxMaService wxMaService;
    private final GlobalSysUserService globalSysUserService;


    public WxMiniAppCodeAuthenticationProvider(
            UserService userService,
            WxMaService wxMaService,
            GlobalSysUserService globalSysUserService) {
        this.userService = userService;
        this.wxMaService = wxMaService;
        this.globalSysUserService = globalSysUserService;
    }


    /**
     * 微信认证逻辑（多租户版本）
     *
     * @param authentication 认证对象
     * @return 认证后的 Authentication 对象
     * @throws AuthenticationException 认证异常
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String code = (String) authentication.getPrincipal();

        // 1. 通过微信服务端验证 code 并获取用户会话信息
        WxMaJscode2SessionResult sessionInfo;
        try {
            sessionInfo = wxMaService.getUserService().getSessionInfo(code);
        } catch (WxErrorException e) {
            throw new CredentialsExpiredException("微信登录 code 无效或已失效，请重新获取");
        }

        String openId = sessionInfo.getOpenid();
        String unionId = sessionInfo.getUnionid();

        if (StrUtil.isBlank(openId)) {
            throw new UsernameNotFoundException("未能获取到微信 OpenID，请稍后重试");
        }

        log.info("微信登录 - OpenID: {}, UnionID: {}", openId, unionId);

        // 2. 从主数据源的 global_sys_user 表查询用户
        GlobalSysUser globalUser = globalSysUserService.getByOpenId(openId);

        if (globalUser == null) {
            // 2.1 用户不存在，注册新用户（保存到主数据源的 global_sys_user 表）
            log.info("用户首次登录，开始注册全局用户，OpenID: {}", openId);
            globalUser = globalSysUserService.registerWechatUser(openId, unionId);

            if (globalUser == null) {
                throw new UsernameNotFoundException("用户注册失败，请稍后重试");
            }

            log.info("全局用户注册成功，用户ID: {}, OpenID: {}", globalUser.getId(), openId);
        } else {
            // 2.2 用户已存在，更新 unionId（如果之前没有保存）
            if (StrUtil.isNotBlank(unionId) && StrUtil.isBlank(globalUser.getUnionid())) {
                globalSysUserService.updateUnionId(globalUser.getId(), unionId);
                globalUser.setUnionid(unionId);
            }
            log.info("找到全局用户，用户ID: {}, OpenID: {}, 租户ID: {}",
                    globalUser.getId(), openId, globalUser.getTenantId());
        }

        // 3. 检查用户状态
        if (ObjectUtil.notEqual(globalUser.getStatus(), 1)) {
            throw new DisabledException("用户已被禁用");
        }

        // 4. 判断用户是否有租户
        if (globalUser.getTenantId() == null) {
            // 4.1 用户无租户：返回基础用户信息，不包含权限
            log.info("用户未加入任何组织，返回无权限信息，用户ID: {}", globalUser.getId());
            return createAuthenticationWithoutTenant(globalUser);
        } else {
            // 4.2 用户有租户：切换到租户数据源，查询权限信息
            log.info("用户已加入组织，切换到租户数据源，租户ID: {}", globalUser.getTenantId());
            return authenticateWithTenant(globalUser);
        }
    }

    /**
     * 创建无租户的认证信息（用户未加入组织）
     *
     * @param globalUser 全局用户信息
     * @return 认证Token
     */
    private Authentication createAuthenticationWithoutTenant(GlobalSysUser globalUser) {
        // 创建最基础的用户认证信息
//        UserAuthCredentials credentials = new UserAuthCredentials();
//        credentials.setUserId(globalUser.getId());
//        credentials.setUsername(globalUser.getUsername() != null ? globalUser.getUsername() : "user_" + globalUser.getId());
//        credentials.setNickname(globalUser.getNickname());
//        credentials.setAvatar(globalUser.getAvatar());
//        credentials.setStatus(globalUser.getStatus());
//        credentials.setMobile(globalUser.getMobile());
//        credentials.setOpenid(globalUser.getOpenid());

        //TODO:改为从数据库从读取用户角色和权限范围
        // 从租户数据库查询用户的详细信息和权限
        UserAuthCredentials userAuthCredentials = globalSysUserService.getAuthCredentialsByOpenId(globalUser.getOpenid());
        // 构建用户详情
        SysUserDetails userDetails = new SysUserDetails(userAuthCredentials);

        // 创建已认证的Token
        return WxMiniAppCodeAuthenticationToken.authenticated(
                userDetails,
                userDetails.getAuthorities()
        );
    }

    /**
     * 带租户信息的认证（用户已加入组织）
     *
     * @param globalUser 全局用户信息
     * @return 认证Token
     */
    private Authentication authenticateWithTenant(GlobalSysUser globalUser) {
        try {
            // 设置租户上下文
            TenantContextHolder.setTenantId(globalUser.getTenantId());
            log.info("设置租户上下文，租户ID: {}", globalUser.getTenantId());

            // 从租户数据库查询用户的详细信息和权限
            UserAuthCredentials userAuthCredentials = userService.getAuthCredentialsByOpenId(globalUser.getOpenid());

            if (userAuthCredentials == null) {
                // 租户数据库中没有该用户（数据不一致）
                log.warn("全局用户存在但租户数据库中不存在，用户ID: {}, 租户ID: {}",
                        globalUser.getId(), globalUser.getTenantId());

                // 可以选择：
                // 方案1：返回无权限信息
                // return createAuthenticationWithoutTenant(globalUser);

                // 方案2：抛出异常
                throw new UsernameNotFoundException("租户数据库中未找到该用户，请联系管理员");
            }

            // 检查租户用户状态
            if (ObjectUtil.notEqual(userAuthCredentials.getStatus(), 1)) {
                throw new DisabledException("该租户下的用户已被禁用");
            }

            // 确保 openid 被设置到 UserAuthCredentials 中（如果查询结果中没有）
            if (userAuthCredentials.getOpenid() == null) {
                userAuthCredentials.setOpenid(globalUser.getOpenid());
            }

            // 构建认证后的用户详情信息（包含权限）
            SysUserDetails userDetails = new SysUserDetails(userAuthCredentials);

            // 设置租户ID到用户详情中
            userDetails.setTenantId(globalUser.getTenantId());

//            log.info("租户用户认证成功，用户ID: {}, 租户ID: {}, 权限数: {}",
//                    userDetails.getUserId(), globalUser.getTenantId(),
//                    userAuthCredentials.getPerms() != null ? userAuthCredentials.getPerms().length : 0);

            // 创建已认证的Token
            return WxMiniAppCodeAuthenticationToken.authenticated(
                    userDetails,
                    userDetails.getAuthorities()
            );

        } catch (Exception e) {
            log.error("租户用户认证失败，用户ID: {}, 租户ID: {}, 错误: {}",
                    globalUser.getId(), globalUser.getTenantId(), e.getMessage(), e);
            throw e;
        } finally {
            // 清理租户上下文
            TenantContextHolder.clear();
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return WxMiniAppCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
